/**
 * @particulars 初始化状态数据（劫持 + 代理）
 */

import { observer } from "./observe";
import Dep from "./observe/dep";
import { nextTick } from "./observe/queue";
import Watcher from "./observe/watcher";

// 1、初始化状态数据
export function initState(vm) {
  // 获取到合并后的options（用户自定义 + 全局定义的）
  const opts = vm.$options;

  // 初始化数据
  if (opts.data) initData(vm);

  // 初始化计算属性
  if (opts.computed) initComputed(vm);

  // 初始化watch
  if (opts.watch) initWatch(vm);
}

// 3、对_data 数据进行代理
function proxyData(vm, target, key) {
  Object.defineProperty(vm, key, {
    get() {
      return vm[target][key];
    },
    set(newValue) {
      if (vm[target][key] === newValue) return;
      vm[target][key] = newValue;
    }
  })
}

// 2、初始化数据
function initData(vm) {
  let data = vm.$options.data;
  // 自定义的data 可能是一个函数，也可能是一个对象，函数需要执行拿到data
  data = typeof data === 'function' ? data.call(vm) : data;

  // 在vm 实例上挂载一个_data，便于对数据进行代理
  vm._data = data;

  // 对数据进行劫持
  observer(data);

  // 使用vm 对vm._data 进行代理，便于直接访问vm.age = vm._data.age
  for (let key in data) {
    proxyData(vm, '_data', key);
  }
}

// 4、初始化计算属性
function initComputed(vm) {
  // 获取到合并后的计算属性
  const computed = vm.$options.computed;
  // 4-1、将计算属性的Watcher 保存到vm 上，便于取值
  const watchers = vm._computedWatcher = {};

  // 4-2、遍历所有的计算属性
  for (let key in computed) {
    const userProperty = computed[key];

    // 我们需要监控computed 中get 的变化
    const get = typeof userProperty === 'function' ? userProperty : userProperty.get;
    // 如果new Watcher 默认就会执行get，将属性和Watcher 对应起来
    watchers[key] = new Watcher(vm, get, { lazy: true });

    // 对计算属性劫持vm => Vue，key => 计算属性名，userProperty => 计算属性的函数体
    defineComputer(vm, key, userProperty);
  }
}

// 5、对计算属性进行劫持
function defineComputer(vm, key, userProperty) {
  // 计算属性的set 一般用不上，不做处理
  const setter = userProperty.set || (() => { });

  // 从vm 实例中获取对应的属性
  Object.defineProperty(vm, key, {
    get: createComputedGetter.call(vm, key),
    set: setter
  });
}

// 6、重写get 方法，计算属性根本不会收集依赖，只是让自己的依赖属性去收集依赖
function createComputedGetter(key) {
  const _this = this;

  // 需要检测是否要执行这个getter
  return function () {
    // 获取到计算属性对应的Watcher
    const watcher = _this._computedWatcher[key];

    // 如果是脏值则需要更新，求值后dirty 为false，不会重复求值
    if (watcher.dirty) watcher.evaluate();

    // 计算属性Watcher 出栈后，还需要渲染Watcher，所以计算属性Watcher 也需要去收集上一层的Watcher
    if (Dep.target) watcher.depend();

    return watcher.value;
  }
}

// 7、初始化watch
function initWatch(vm) {
  const watch = vm.$options.watch;

  for (let key in watch) {
    let target = watch[key];

    if (Array.isArray(target)) {
      // 处理数组类型的watch
      for (let i = 0; i < target.length; i++) {
        createWatcher(vm, key, target[i]);
      }
    } else if (typeof target === 'function') {
      // 处理函数类型的watch
      createWatcher(vm, key, target);
    } else {
      // 处理对象类型的watch
      const { deep, immediate } = target;
      target = target.handler;

      createWatcher(vm, key, target, { deep, immediate });
    }
  }
}

// 8、创建watch，执行逻辑全部交给$watch
function createWatcher(vm, key, handler, options) {
  // 针对于数组类型的watch，在methods 里面获取对应的方法
  if (typeof handler === 'string') handler = vm[handler];

  // 传入用户watch 的键名，
  return vm.$watch(key, handler, options);
}

// 9、用户的watch，同样需要添加Watcher
function watch(exprOrFn, cb, options = {}) {
  // exprOrFn 是用户定义的watch 键名，{ user: true } 标识为用户的watch，并将用户定义的watch 函数传入
  new Watcher(this, exprOrFn, { user: true }, cb, options);
}

// 10、挂载$nextTick、$watch 函数
export function initStateMixin(Vue) {
  Vue.prototype.$nextTick = nextTick;
  Vue.prototype.$watch = watch;
}